Repository navigation
Fix context ring accuracy: count cache tokens, document Claude windows, add a per-thread breakdown - #447
Merged
currentbits merged 6 commits intoAug 18, 2026
Conversation
Hover/focus the header ring for an estimated split of the last turn (tool output, transcript, system+tool defs). Prefer the CLI-reported window over the static catalog, and warn in amber when fill crosses 85%.
Claude's input_tokens excludes cache_read/cache_creation, so the ring read ~0% then jumped. Sum those fields into contextTokens only; leave the billable in/out counters alone. Codex token_count.last_token_usage is the turn delta and model_context_window is the live window; never add the cumulative total. Kimi cannot be measured, so contextTokens stays unset.
Every claude modelInfo entry lacked contextTokens, so contextWindowFor returned null and the ring never rendered for claude threads — 161 usage rows in the live store all had a measured contextTokens and no denominator to divide by. Values from Anthropic model docs: 1M for Fable 5 / Opus 5 / Sonnet 5, 200k for Haiku 4.5. Refs #317 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The runner slice and the renderer slice were built in parallel against mocks of each other. This drives real provider payloads — a verbatim claude result.usage captured from the live CLI — through the actual ring and breakdown functions, and pins the regression: the same turn reads 14% now and 0% under the pre-fix input+output formula. Refs #317 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
currentbits
deleted the
coder/context-visibility-accurate-per-thread-c-a8891f
branch
October 3, 2026 16:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #317.
The context ring was reading ~0% on every Claude thread and never rendering at all on most of them. Both causes were measurement bugs, not display bugs.
The ring was dividing by the wrong numerator
Claude's
usage.input_tokenscounts only uncached input. With prompt caching on — which is always, in practice — a realresultevent from the CLI looks like this:{"input_tokens": 10, "cache_creation_input_tokens": 10203, "cache_read_input_tokens": 17748, "output_tokens": 175}runner.jscomputed context fill asinput + output, so it read 185 tokens against a true prompt of 28,136 — a 152x under-read. A mid-session thread pulled from a real transcript showedinput_tokens: 2againstcache_read_input_tokens: 154815. That is exactly the "meter stuck near zero, then jumps to 100% at compaction" failure the issue describes, and it was in our own code rather than a provider quirk.contextTokensnow sums input + cache-creation + cache-read + output. The billableinputTokens/outputTokenscounters andrecordUsageare deliberately left alone — cache reads bill differently, so folding them into the cost path would trade one wrong number for another. grok runs the same claude-stream path and inherits the fix.The ring never rendered for Claude at all
Found by scanning the live
coder-store.jsonrather than by a test: everyclaudeentry in the provider catalog was missingcontextTokens, socontextWindowForreturned null and the ring hid itself. 161 stored usage rows had a measured numerator and no denominator to divide by. Added the documented windows — 1M for Fable 5 / Opus 5 / Sonnet 5, 200k for Haiku 4.5.Per-provider accuracy
token_count.info.total_token_usageis session-cumulative but was being added as if it were a per-turn delta, inflating totals on every event. Now useslast_token_usagefor the delta and treats totals-only events as a snapshot to replace rather than add.info.model_context_windowflows into the newSessionUsage.contextWindowand takes precedence over the static catalog, which goes stale when a thread changes model.contextTokensstays unset and the ring hides.The rule applied throughout: if a provider cannot be measured accurately, show no number. An inaccurate meter is worse than none.
Breakdown UI
New pure
src/contextBreakdown.tssplits the measured total into tool output, transcript, and a remainder attributed to the system prompt and tool definitions. Segments are estimated at chars/4 with no tokenizer dependency; the provider's measured total stays authoritative, segments never sum above it, and the popover labels itself as an estimate rather than implying precision it doesn't have. Hovering or focusing the ring opens it; fill at or above 85% turns the ring amber and warns that compaction is close.Testing
Renderer 926 pass / 0 fail, electron 1166 pass / 1 fail — that one failure is the pre-existing web-integration reconnect test, which fails on
maintoo.tsc --noEmitclean.The electron and renderer halves were built in parallel, each against a mock of the other, so
test/contextSeam.test.tscovers the join they couldn't: a verbatimresult.usagepayload captured from the live CLI, driven through the real ring and breakdown functions. It pins the regression directly — the same turn reads 14% now and 0% under the old formula.Not included
The account-level rate-limit and reset-time popover from the issue comments. That needs provider quota endpoints (Claude OAuth usage, ChatGPT
wham/usage), which is a different data source from per-thread token accounting and belongs in its own change.Also filed #441 separately:
electron/providers.jscontains a literal NUL byte, which makes grep silently return zero matches on that file. Left unfixed here to keep this diff scoped.